package cn.song.util;

import cn.song.annotation.ExcelImageProperty;
import cn.song.config.convert.ExcelUrlImageConverter;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ss.usermodel.PictureData;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;

import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Excel图片读取工具类
 *
 * <p>此工具类提供了从Excel文件中提取图片并保存到本地文件系统的功能。
 * 支持读取Excel 2007(.xlsx)格式文件中的嵌入图片，并能够根据图片在表格中的位置
 * 自动分类保存和命名。</p>
 *
 * <p>主要功能：</p>
 * <ul>
 *   <li>图片提取 - 从.xlsx文件中读取所有嵌入图片</li>
 *   <li>位置识别 - 自动识别图片在表格中的行列位置</li>
 *   <li>文件保存 - 自动命名并保存图片到指定目录</li>
 *   <li>数据绑定 - 根据注解将图片路径设置到对应的对象字段</li>
 *   <li>格式支持 - 支持JPG、PNG、GIF等常见图片格式</li>
 * </ul>
 *
 * <p>技术实现：</p>
 * <ul>
 *   <li>Apache POI - 使用POI库解析Excel文件结构</li>
 *   <li>XSSFWorkbook - 支持Excel 2007+格式</li>
 *   <li>XSSFDrawing - 处理Excel中的绘图对象</li>
 *   <li>XSSFPicture - 提取图片数据和位置信息</li>
 *   <li>反射机制 - 自动识别和设置图片相关字段</li>
 * </ul>
 *
 * <p>使用示例：</p>
 * <pre>{@code
 * // 读取Excel文件中的图片并保存到本地
 * List<DemoExcelInput> dataList = new ArrayList<>();
 * try (InputStream inputStream = new FileInputStream("example.xlsx")) {
 *     ExcelReadImageUtil.readImage(inputStream, dataList);
 * }
 * }</pre>
 *
 * @author SongRenShuo
 * @version 1.0
 * @since 2025-01-21
 * @see org.apache.poi.xssf.usermodel.XSSFWorkbook
 * @see org.apache.poi.xssf.usermodel.XSSFPicture
 * @see cn.song.annotation.ExcelImageProperty
 */
@Slf4j
public class ExcelReadImageUtil {


    /**
     * 从Excel文件中读取和提取所有嵌入图片
     *
     * <p>此方法使用Apache POI库解析Excel文件，提取其中的所有图片并保存到本地文件系统。
     * 支持自动识别图片位置、格式检测、文件命名和数据绑定等功能。</p>
     *
     * <p>处理流程：</p>
     * <ol>
     *   <li>Ⅰ. 使用POI WorkbookFactory创建工作簿对象</li>
     *   <li>Ⅱ. 获取默认工作表（第一个工作表）</li>
     *   <li>Ⅲ. 遍历工作表中的所有绘图对象</li>
     *   <li>Ⅳ. 提取图片数据、位置信息和格式</li>
     *   <li>Ⅴ. 自动生成文件名并保存图片</li>
     *   <li>Ⅵ. 根据注解配置自动设置对象字段值</li>
     * </ol>
     *
     * <p>文件命名规则：</p>
     * <ul>
     *   <li>格式：image_[序号]_row[行号]_col[列号].[扩展名]</li>
     *   <li>示例：image_1_row2_col3.jpg</li>
     *   <li>保存目录：output/readImage/</li>
     * </ul>
     *
     * @param <T> 数据对象类型，需要包含@ExcelImageProperty注解的字段
     * @param inputStream Excel文件输入流，支持.xlsx格式
     * @param list 数据对象列表，用于存储图片路径信息（可为空列表）
     * @throws IOException 当文件读取失败、图片提取失败或文件保存失败时抛出
     * @since 1.0
     * @apiNote 无论传入的list大小如何，都会提取所有图片并保存到本地
     * @see ExcelImageProperty 图片字段注解，用于指定图片对应的列位置
     */
    public static <T> void readImage(InputStream inputStream, List<T> list) throws IOException {
        readImage(inputStream, list, "output/readImage/");
    }

    /**
     * 使用EasyExcel读取Excel数据并处理图片（支持@ExcelProperty注解）
     *
     * <p>此方法结合EasyExcel的@ExcelProperty注解读取功能和POI的图片提取功能，
     * 实现Excel数据读取的同时自动提取图片并将文件路径设置到对应字段中。</p>
     *
     * <p>处理流程：</p>
     * <ol>
     *   <li>Ⅰ. 使用POI创建工作簿对象进行图片提取</li>
     *   <li>Ⅱ. 提取所有图片并保存到指定路径</li>
     *   <li>Ⅲ. 创建图片位置映射表</li>
     *   <li>Ⅳ. 使用EasyExcel读取数据，支持@ExcelProperty注解</li>
     *   <li>Ⅴ. 在数据读取过程中根据@ExcelImageProperty注解设置图片路径</li>
     * </ol>
     *
     * <p>注解支持：</p>
     * <ul>
     *   <li>@ExcelProperty - 标准EasyExcel注解，支持列映射</li>
     *   <li>@ExcelImageProperty - 自定义注解，指定图片字段对应的列位置</li>
     * </ul>
     *
     * @param <T> 数据对象类型，需要包含@ExcelProperty和@ExcelImageProperty注解的字段
     * @param inputStream Excel文件输入流，支持.xlsx格式
     * @param clazz 数据对象的Class类型
     * @param filePath 图片保存的基础路径，应以路径分隔符结尾
     * @return 包含数据和图片路径的对象列表
     * @throws IOException 当文件读取失败、图片提取失败或文件保存失败时抛出
     * @since 1.0
     * @apiNote 支持@ExcelProperty注解进行列映射，避免通过下标获取数据
     * @see ExcelProperty EasyExcel标准列映射注解
     * @see ExcelImageProperty 自定义图片位置注解
     */
    public static <T> List<T> readImageWithAnnotation(InputStream inputStream, Class<T> clazz, String filePath) throws IOException {
        long startTime = System.currentTimeMillis();

        // Ⅰ. 首先提取所有图片并建立位置映射
        Map<String, String> imagePositionMap = extractAllImages(inputStream, filePath);

        // Ⅱ. 使用EasyExcel读取数据
        List<T> dataList = new ArrayList<>();

        try (InputStream dataInputStream = inputStream) {
            // 重置输入流以便EasyExcel读取
            inputStream.reset();
        } catch (IOException e) {
            // 如果输入流不支持reset，则需要重新创建
            log.warn("输入流不支持reset操作，请确保传入的是支持标记的输入流");
            throw new IOException("输入流需要支持reset操作或传入ByteArrayInputStream", e);
        }

        // 创建自定义监听器
        ImageDataListener<T> listener = new ImageDataListener<>(dataList, imagePositionMap, filePath);

        // 使用EasyExcel读取数据
        EasyExcel.read(inputStream, clazz, listener)
                .sheet()
                .doRead();

        long totalTime = System.currentTimeMillis() - startTime;
        log.info("完成Excel数据和图片处理，共读取 {} 条数据，提取 {} 张图片，总耗时: {} ms",
                dataList.size(), imagePositionMap.size(), totalTime);

        return dataList;
    }

    /**
     * 从Excel文件中读取和提取所有嵌入图片（支持自定义保存路径）
     *
     * <p>此方法使用Apache POI库解析Excel文件，提取其中的所有图片并保存到指定的本地文件系统路径。
     * 支持自动识别图片位置、格式检测、文件命名和数据绑定等功能。相比基础版本，增加了自定义保存路径的能力。</p>
     *
     * <p>处理流程：</p>
     * <ol>
     *   <li>Ⅰ. 使用POI WorkbookFactory创建工作簿对象</li>
     *   <li>Ⅱ. 获取默认工作表（第一个工作表）</li>
     *   <li>Ⅲ. 遍历工作表中的所有绘图对象</li>
     *   <li>Ⅳ. 提取图片数据、位置信息和格式</li>
     *   <li>Ⅴ. 根据自定义路径生成文件名并保存图片</li>
     *   <li>Ⅵ. 根据注解配置自动设置对象字段值</li>
     * </ol>
     *
     * <p>文件命名规则：</p>
     * <ul>
     *   <li>格式：image_[序号]_row[行号]_col[列号].[扩展名]</li>
     *   <li>示例：image_1_row2_col3.jpg</li>
     *   <li>保存目录：由filePath参数指定</li>
     * </ul>
     *
     * @param <T> 数据对象类型，需要包含@ExcelImageProperty注解的字段
     * @param inputStream Excel文件输入流，支持.xlsx格式
     * @param list 数据对象列表，用于存储图片路径信息（可为空列表）
     * @param filePath 图片保存的基础路径，应以路径分隔符结尾
     * @throws IOException 当文件读取失败、图片提取失败或文件保存失败时抛出
     * @since 1.0
     * @apiNote 无论传入的list大小如何，都会提取所有图片并保存到指定路径
     * @see ExcelImageProperty 图片字段注解，用于指定图片对应的列位置
     */
    public static <T> void readImage(InputStream inputStream, List<T> list, String filePath) throws IOException {
        long startTime = System.currentTimeMillis();

        // Ⅰ. 使用POI WorkbookFactory创建工作簿对象，自动检测Excel格式——关键性能监控点
        long workbookStartTime = System.currentTimeMillis();
        Workbook workbook = WorkbookFactory.create(inputStream);
        long workbookTime = System.currentTimeMillis() - workbookStartTime;

        if (log.isDebugEnabled()) {
            log.debug("POI工作簿创建耗时: {} ms", workbookTime);
        }

        // Ⅱ. 获取默认工作表（第一个工作表）
        XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0);

        // 获取工作表中的所有相关对象（包括绘图、图表等）
        List<POIXMLDocumentPart> documentPartList = sheet.getRelations();
        int size = list.size();

        // 初始化图片计数器，用于生成唯一文件名
        int imageCounter = 0;
        int totalImagesProcessed = 0;

        if (log.isDebugEnabled()) {
            log.debug("开始从 Excel 文件中提取图片，工作表关联对象数量: {}, 数据列表大小: {}, 保存路径: {}",
                    documentPartList.size(), size, filePath);
        }

        // Ⅲ. 遍历工作表中的所有相关对象，查找绘图对象
        for (POIXMLDocumentPart part : documentPartList) {
            // 过滤非绘图对象
            if (!(part instanceof XSSFDrawing)) {
                continue;
            }

            XSSFDrawing drawing = (XSSFDrawing) part;
            List<XSSFShape> shapes = drawing.getShapes();

            log.debug("找到绘图对象，包含形状数量: {}", shapes.size());

            // 遍历绘图中的所有形状对象
            for (XSSFShape shape : shapes) {
                // 过滤非图片对象
                if (!(shape instanceof XSSFPicture)) {
                    continue;
                }

                XSSFPicture picture = (XSSFPicture) shape;

                // Ⅳ. 获取图片在表格中的位置信息
                XSSFClientAnchor anchor = picture.getPreferredSize();
                CTMarker marker = anchor.getFrom();
                int row = marker.getRow() - 1;
                int col = marker.getCol();

                log.debug("发现图片，位置: 行{} 列{}", row, col);

                // 检查行索引的有效性
                if (row < 0) {
                    log.warn("图片行索引无效: {}，跳过处理", row);
                    continue;
                }

                // 获取图片原始数据
                PictureData pictureData = picture.getPictureData();
                String extension = pictureData.suggestFileExtension();
                byte[] bytes = pictureData.getData();
                InputStream imageInputStream = new ByteArrayInputStream(bytes);

                log.debug("图片信息: 格式={}, 大小={} bytes", extension, bytes.length);

                // Ⅴ. 生成唯一的图片文件名（包含位置信息和计数器）
                String fileName = "image_" + imageCounter + "_row" + row + "_col" + col + "." + extension;

                // 保存图片到指定目录
                String fullFilePath = saveInputStreamToFile(imageInputStream, filePath + fileName);
                if (fullFilePath != null) {
                    log.info("成功提取并保存图片: {} (位置: 行{}, 列{}, 大小: {} bytes)",
                            fileName, row, col, bytes.length);
                } else {
                    log.error("图片保存失败: {}", fileName);
                    continue;
                }

                imageCounter++;
                totalImagesProcessed++;

                // Ⅵ. 如果数据列表不为空且行索引在有效范围内，则设置对应的字段值
                if (row < size) {
                    T item = list.get(row);
                    setFieldImage(item, col, fullFilePath);
                }
            }
        }

        // 关闭工作簿资源
        workbook.close();

        // 统计总耗时和性能指标
        long totalTime = System.currentTimeMillis() - startTime;
        double avgTimePerImage = totalImagesProcessed > 0 ? (double) totalTime / totalImagesProcessed : 0.0;

        log.info("完成Excel图片提取，共处理 {} 张图片，总耗时: {} ms [工作簿创建:{} ms, 平均:{} ms/图]",
                totalImagesProcessed, totalTime, workbookTime, avgTimePerImage);
    }

    /**
     * 提取Excel中的所有图片并返回位置映射
     *
     * <p>此方法专门用于提取Excel中的所有图片，并建立行列位置到图片路径的映射关系。</p>
     *
     * @param inputStream Excel文件输入流
     * @param filePath 图片保存路径
     * @return 位置映射表，key为"row_col"格式，value为图片相对路径
     * @throws IOException 当文件处理失败时抛出
     */
    private static Map<String, String> extractAllImages(InputStream inputStream, String filePath) throws IOException {
        Map<String, String> imagePositionMap = new ConcurrentHashMap<>();

        // 使用POI创建工作簿
        long workbookStartTime = System.currentTimeMillis();
        Workbook workbook = WorkbookFactory.create(inputStream);
        long workbookTime = System.currentTimeMillis() - workbookStartTime;

        log.debug("POI工作簿创建耗时: {} ms", workbookTime);

        try {
            XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0);
            List<POIXMLDocumentPart> documentPartList = sheet.getRelations();

            int imageCounter = 0;

            // 遍历所有绘图对象
            for (POIXMLDocumentPart part : documentPartList) {
                if (!(part instanceof XSSFDrawing)) {
                    continue;
                }

                XSSFDrawing drawing = (XSSFDrawing) part;
                List<XSSFShape> shapes = drawing.getShapes();

                for (XSSFShape shape : shapes) {
                    if (!(shape instanceof XSSFPicture)) {
                        continue;
                    }

                    XSSFPicture picture = (XSSFPicture) shape;
                    XSSFClientAnchor anchor = picture.getPreferredSize();
                    CTMarker marker = anchor.getFrom();
                    // 行索引减1，因为Excel行索引从0开始
                    int row = marker.getRow() - 1;
                    int col = marker.getCol();

                    if (row < 0) {
                        log.warn("图片行索引无效: {}，跳过处理", row);
                        continue;
                    }

                    // 获取图片数据
                    PictureData pictureData = picture.getPictureData();
                    String extension = pictureData.suggestFileExtension();
                    byte[] bytes = pictureData.getData();
                    InputStream imageInputStream = new ByteArrayInputStream(bytes);

                    // 生成文件名
                    String fileName = "image_" + imageCounter + "_row" + row + "_col" + col + "." + extension;
                    String fullFilePath = saveInputStreamToFile(imageInputStream, filePath + fileName);

                    if (fullFilePath != null) {
                        // 生成相对路径（去掉绝对路径前缀）
                        String relativePath = filePath + fileName;
                        // 建立位置映射："row_col" -> 相对路径
                        String positionKey = row + "_" + col;
                        imagePositionMap.put(positionKey, relativePath);

                        log.debug("成功提取图片: {} -> 位置: 行{}, 列{}, 映射键: {}",
                                fileName, row, col, positionKey);
                        imageCounter++;
                    }
                }
            }
        } finally {
            workbook.close();
        }

        return imagePositionMap;
    }

    /**
     * 通过反射机制自动设置对象中的图片相关字段值
     *
     * <p>此方法根据@ExcelImageProperty注解的配置，将图片文件路径设置到对应的对象字段中。
     * 支持自动匹配列位置和字段的对应关系。</p>
     *
     * <p>匹配逻辑：</p>
     * <ol>
     *   <li>Ⅰ. 获取对象的所有字段</li>
     *   <li>Ⅱ. 遍历字段查找ExcelImageProperty注解</li>
     *   <li>Ⅲ. 比较注解中index与传入的列索引</li>
     *   <li>Ⅳ. 匹配成功后使用反射设置字段值</li>
     * </ol>
     *
     * @param <T> 数据对象类型
     * @param item 目标数据对象实例
     * @param colIndex 列索引（从1开始，与Excel显示一致）
     * @param filePath 图片文件的完整路径
     * @since 1.0
     * @apiNote 方法会自动处理字段访问权限，支持private字段的设置
     * @see ExcelImageProperty 图片字段注解，必须指定index属性
     */
    private static <T> void setFieldImage(T item, int colIndex, String filePath) {
        // Ⅰ. 获取对象的Class对象和所有字段
        Class<?> clazz = item.getClass();
        Field[] fields = clazz.getDeclaredFields();

        log.debug("尝试为对象 {} 设置第 {} 列的图片路径: {}", clazz.getSimpleName(), colIndex, filePath);

        // Ⅱ. 遍历所有字段，查找带有ExcelImageProperty注解的字段
        for (Field field : fields) {
            // 检查字段是否有ExcelImageProperty注解
            if (!field.isAnnotationPresent(ExcelImageProperty.class)) {
                continue;
            }

            // 获取注解并提取index属性
            ExcelImageProperty excelImageProperty = field.getAnnotation(ExcelImageProperty.class);
            // 图片在第几列 从0开始
            int index = excelImageProperty.index();

            // Ⅲ. 比较注解中index与传入的列索引
            if (index == colIndex) {
                try {
                    // Ⅳ. 设置字段可访问（处理private字段）
                    field.setAccessible(true);

                    // 使用反射设置字段值
                    field.set(item, filePath);

                    log.debug("成功设置字段 {} 的图片路径: {}", field.getName(), filePath);
                } catch (IllegalAccessException e) {
                    log.error("设置字段 {} 的图片路径失败", field.getName(), e);
                }
                // 找到匹配字段后立即退出循环（一个列只能对应一个字段）
                break;
            }
        }
    }

    /**
     * 根据行号和图片位置映射设置图片路径到对象字段
     *
     * @param <T> 数据对象类型
     * @param item 数据对象实例
     * @param rowIndex 数据行号（从1开始）
     * @param imagePositionMap 图片位置映射表
     */
    private static <T> void setImagePathsToFields(T item, int rowIndex, Map<String, String> imagePositionMap) {
        Class<?> clazz = item.getClass();
        Field[] fields = clazz.getDeclaredFields();

        // 遍历所有字段，查找带有ExcelImageProperty注解的字段
        for (Field field : fields) {
            if (!field.isAnnotationPresent(ExcelImageProperty.class)) {
                continue;
            }

            ExcelImageProperty annotation = field.getAnnotation(ExcelImageProperty.class);
            int colIndex = annotation.index();

            // 构建位置键
            String positionKey = rowIndex + "_" + colIndex;
            String imagePath = imagePositionMap.get(positionKey);

            if (imagePath != null) {
                try {
                    field.setAccessible(true);
                    field.set(item, imagePath);
                    log.debug("成功设置字段 {} 的图片路径: {}", field.getName(), imagePath);
                } catch (IllegalAccessException e) {
                    log.error("设置字段 {} 的图片路径失败", field.getName(), e);
                }
            }
        }
    }


    /**
     * 安全地将输入流中的数据保存到指定文件
     *
     * <p>此方法提供了安全、高效的文件保存功能，支持自动创建目录、
     * 流式数据传输和资源自动清理。适用于图片、文档等二进制文件的保存。</p>
     *
     * <p>保存流程：</p>
     * <ol>
     *   <li>Ⅰ. 检查并创建目标文件的父目录</li>
     *   <li>Ⅱ. 创建目标文件对象</li>
     *   <li>Ⅲ. 使用缓冲区流式传输数据</li>
     *   <li>Ⅳ. 自动关闭资源并处理异常</li>
     * </ol>
     *
     * <p>安全特性：</p>
     * <ul>
     *   <li>自动创建父目录 - 避免目录不存在导致的保存失败</li>
     *   <li>try-with-resources - 保证资源自动释放</li>
     *   <li>异常处理 - 提供详细的错误信息和日志</li>
     *   <li>1KB缓冲区 - 平衡内存使用和传输效率</li>
     * </ul>
     *
     * @param inputStream 源数据输入流，方法不会关闭此流
     * @param filePath 目标文件路径，支持相对路径和绝对路径
     * @return 保存成功返回文件路径，失败返回null
     * @since 1.0
     * @apiNote 方法会自动创建不存在的父目录，但不会覆盖已存在的文件
     */
    public static String saveInputStreamToFile(InputStream inputStream, String filePath) {
        // Ⅰ. 创建目标文件对象并检查父目录
        File file = new File(filePath);

        // 检查并创建父目录（如果不存在）
        File parentDir = file.getParentFile();
        if (parentDir != null && !parentDir.exists()) {
            boolean created = parentDir.mkdirs();
            if (created && log.isDebugEnabled()) {
                log.debug("成功创建目录: {}", parentDir.getAbsolutePath());
            } else if (!created) {
                log.warn("创建目录失败: {}", parentDir.getAbsolutePath());
            }
        }

        // Ⅱ. 使用try-with-resources自动管理资源，流式传输数据
        try (FileOutputStream outputStream = new FileOutputStream(file)) {
            // 使用8KB缓冲区提高传输效率（从1KB优化到8KB）
            byte[] buffer = new byte[8192];
            int bytesRead;
            long totalBytesWritten = 0;
            long startTime = System.currentTimeMillis();

            // Ⅲ. 循环读取输入流并写入输出流
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
                totalBytesWritten += bytesRead;
            }

            // 确保数据完全写入磁盘
            outputStream.flush();

            long saveTime = System.currentTimeMillis() - startTime;
            if (log.isDebugEnabled()) {
                log.debug("文件已成功保存到: {}, 大小: {} bytes, 耗时: {} ms",
                        filePath, totalBytesWritten, saveTime);
            } else {
                log.info("文件已成功保存到: {}, 大小: {} bytes", filePath, totalBytesWritten);
            }
            return filePath;

        } catch (IOException e) {
            // Ⅳ. 详细的异常处理和日志记录
            log.error("保存文件异常, 目标路径: {}, 错误信息: {}", filePath, e.getMessage(), e);
            return null;
        }
    }


    /**
     * EasyExcel数据读取监听器（支持图片处理）
     *
     * <p>此监听器结合EasyExcel的数据读取功能和图片路径设置功能，
     * 在数据读取过程中自动根据@ExcelImageProperty注解设置图片路径。</p>
     *
     * @param <T> 数据对象类型
     */
    @Getter
    private static class ImageDataListener<T> implements ReadListener<T> {
        /** 数据列表 */
        private final List<T> dataList;
        /** 图片位置映射表 */
        private final Map<String, String> imagePositionMap;
        /** 文件路径 */
        private final String filePath;

        public ImageDataListener(List<T> dataList, Map<String, String> imagePositionMap, String filePath) {
            this.dataList = dataList;
            this.imagePositionMap = imagePositionMap;
            this.filePath = filePath;
        }

        /**
         * 分析时，一行触发调用函数。
         *
         * @param data 数据
         * @param context 上下文
         * @date 2025/08/28
         * @author SongRenShuo
         *
         */
        @Override
        public void invoke(T data, AnalysisContext context) {
            // 获取当前数据行号（RowIndex从1开始），这里减1是因为行索引从0开始
            int rowIndex = context.readRowHolder().getRowIndex() - 1;

            // 设置图片路径到对应字段
            setImagePathsToFields(data, rowIndex, imagePositionMap);

            // 添加到数据列表
            dataList.add(data);

            log.debug("读取到数据: 行{}, 对象: {}", rowIndex, data);
        }

        /**
         * 分析过后再做
         *
         * @param context 上下文
         * @date 2025/08/28
         * @author SongRenShuo
         *
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            log.info("Excel数据读取完成，共读取 {} 条记录", dataList.size());
        }
    }

    /**
     * Excel数据输入演示类
     *
     * <p>此内部类作为从Excel文件中读取包含图片数据的演示模型。
     * 展示了如何使用EasyExcel注解和自定义注解来处理包含图片的Excel数据。</p>
     *
     * <p>支持的图片数据格式：</p>
     * <ul>
     *   <li>imageStr - 图片URL字符串，用于EasyExcel的图片转换器</li>
     *   <li>imageUrl - java.net.URL对象，支持网络和本地文件URL</li>
     *   <li>inputStream - InputStream对象，支持流式图片数据</li>
     *   <li>bytes - byte[]数组，支持原始二进制图片数据</li>
     * </ul>
     *
     * <p>注解说明：</p>
     * <ul>
     *   <li>@ExcelProperty - EasyExcel标准注解，用于映射Excel列名</li>
     *   <li>@ExcelImageProperty - 自定义注解，指定图片对应的列位置</li>
     *   <li>ExcelUrlImageConverter - 图片URL转换器，将URL转换为图片数据</li>
     * </ul>
     *
     * @since 1.0
     * @see com.alibaba.excel.annotation.ExcelProperty EasyExcel列映射注解
     * @see cn.song.annotation.ExcelImageProperty 自定义图片位置注解
     * @see cn.song.config.convert.ExcelUrlImageConverter 图片URL转换器
     */
    @Data
    public static class DemoExcelInput {

        /**
         * 产品名称字段
         * 对应Excel中的"姓名"列
         */
        @ExcelProperty("姓名")
        private String name;

        /**
         * 图片数据字段（主URL格式）
         * 使用ExcelUrlImageConverter转换器将URL字符串转换为图片数据
         * &#064;ExcelImageProperty指定对应Excel中的第2列
         */
        @ExcelProperty(value = "图片", converter = ExcelUrlImageConverter.class)
        @ExcelImageProperty(index = 1)
        private String imageStr;

        /**
         * 图片URL对象字段
         * 支持java.net.URL类型，可以是网络地址或本地文件地址
         */
        @ExcelProperty("url")
        private URL imageUrl;

        /**
         * 图片数据流字段
         * 支持InputStream类型，适用于流式图片数据处理
         */
        @ExcelProperty("inputstream")
        private InputStream inputStream;

        /**
         * 图片二进制数据字段
         * 支持byte[]类型，适用于原始二进制图片数据存储
         */
        @ExcelProperty("bytes")
        private byte[] bytes;
    }


    /**
     * Excel图片导入数据模型
     *
     * <p>此类用于EasyExcel读取包含图片的Excel文件时的数据映射。
     * 支持自动提取图片并将文件路径回显到对应字段中。</p>
     *
     * <p>主要功能：</p>
     * <ul>
     *   <li>使用@ExcelProperty注解进行Excel列映射</li>
     *   <li>使用@ExcelImageProperty注解标识图片字段位置</li>
     *   <li>自动提取图片并设置文件路径到字段</li>
     *   <li>支持多列图片数据处理</li>
     * </ul>
     *
     * <p>使用示例：</p>
     * <pre>{@code
     *          List<ExcelImageImportModel> dataList;
     *          try (InputStream inputStream = new ByteArrayInputStream(file.getBytes())) {
     *               dataList = ExcelReadImageUtil.readImageWithAnnotation(inputStream, ExcelImageImportModel.class, outputDir);
     *           }
     * }</pre>
     *
     * @author SongRenShuo
     * @version 1.0
     * @since 2025-08-28
     * @see com.alibaba.excel.annotation.ExcelProperty EasyExcel列映射注解
     * @see cn.song.annotation.ExcelImageProperty 自定义图片位置注解
     */
    @Data
    public static class ExcelImageImportModel {

        /**
         * 姓名字段
         * 对应Excel中的"姓名"列（第1列）
         */
        @ExcelProperty(value = "姓名", index = 0)
        private String name;

        /**
         * 年龄字段
         * 对应Excel中的"年龄"列（第2列）
         */
        @ExcelProperty(value = "年龄", index = 1)
        private Integer age;

        /**
         * 图片字段1
         * 对应Excel中的"图片1"列（第3列）
         * 图片提取后，此字段将包含图片文件的相对路径
         */
        @ExcelProperty(value = "图片1", index = 2)
        @ExcelImageProperty(index = 2)
        private String imagePath1;

        /**
         * 图片字段2
         * 对应Excel中的"图片2"列（第4列）
         * 图片提取后，此字段将包含图片文件的相对路径
         */
        @ExcelProperty(value = "图片2", index = 3)
        @ExcelImageProperty(index = 3)
        private String imagePath2;

        /**
         * 备注字段
         * 对应Excel中的"备注"列（第5列）
         */
        @ExcelProperty(value = "备注", index = 4)
        private String remark;

        /**
         * 数据行号
         * 用于记录数据在Excel中的行号，便于图片提取时的位置匹配
         */
        private Integer rowIndex;
    }

}
